Downloading Content

Latest update: August 2013

In this tutorial, we will show you how to download content from your FlashAir device. We will use command.cgi to do this. This tutorial builds off of Android Tutorial 2: Getting A List of Contents.

We will fetch a list of the current contents of your FlashAir, parse the list, and display the files in a ListView. If you click any folder in the list, it will open and show its contents. If you click on an image, it will download the image to your Android device as well as display it in the application.

We will set up a layout file so that the name of the current directory and the number of items in that directory will be displayed above the list of directory contents. We will also create a Button that will allow us to move back to the parent directory of the directory we are currently in (ex. if we are in 'DCIM/106 _05/', pressing the back button would take us back to 'DCIM/').

The content list will be displayed like this:

displayed directory content list

If you click on an image file in the list, the image will be displayed like this:

display image in ImageView

As you are viewing the image, the image file will download to your Android device.

In order to make this application, we will create the following files:

  • MainActivity.java
  • activity_main.xml
  • ImageViewActivity.java
  • activity_image_view.xml

Important: Please note that your project contains a file called AndroidManifest.xml. This file gives your application particular permissions. By default, applications are not permitted to access the internet. The path to this file should look something like: [Project_Folder]/AndroidManifest.xml
You will need to add the following lines of code into your AndroidManifest.xml in order for this application to work:

    <uses-permission android:name="android.permission.INTERNET" />

Creating the List Layout

First, we will write the activity_main.xml file that determines the layout of our Android App. This can be found in your layout folder. The path to this file should look something like: [Project_Folder]/res/layout/activity_main.xml

You want the activity_main.xml file to look like this:

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="#00000000"
        android:text="Back"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:scaleType="centerInside"
        android:text="Directory Name"
        android:textAlignment="center"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:scaleType="centerInside"
        android:text="Number of files"
        android:textAlignment="center"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textSize="20sp" />

    <ListView
        android:id="@+id/listView1"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:cacheColorHint="#00000000"
        android:clickable="true"
        android:headerDividersEnabled="false" />

</LinearLayout>

Creating the Image Viewing Layout

Next, we will edit the activity_image_view.xml file that determines the layout of our image viewing screen. This can also be found in your layout folder. The path to this file should look something like: [Project_Folder]/res/layout/activity_image_view.xml

You want the activity_image_view.xml file to look like this:

activity_image_view.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ImageViewActivity" >          

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="#00000000"
        android:text="Back"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <ImageView
       android:id="@+id/imageView1"
       android:layout_width="match_parent"
       android:layout_height="fill_parent"
       android:scaleType="centerInside"
       android:contentDescription="view image"
       android:text="image would be here" />
</LinearLayout>

Creating the Image Viewing Activity

Next, we will edit the AndroidManifest.xml file that adds the activity of our image viewing screen. The path to this file should look something like: [Project_Folder]/AndroidManifest.xml

You want the <application> tag in the AndroidManifest.xml file to look like this:

AndroidManifest.xml

    <activity
        android:name="com.example.android_tutorial_03.ImageViewActivity"
        android:label="@string/app_name" >
    </activity>

Creating the Content List

Now we will modify the MainActivity.java file. It should look like this by default:

MainActivity.java

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}

Initialization

We will start by changing the class declaration, since we want to include a list that contains clickable items.

MainActivity.java (1)

public class MainActivity extends Activity implements AdapterView.OnItemClickListener {

We will start by declaring the series of views that we are planning to use, other class variables, and the formatting of the screen. We will also override the onCreate(Bundle savedInstanceState) function that initializes the Activity class. We want the initialization function to set up the default screen layout for our list of contents as well as set a click listener for the Button that we will use to navigate to the parent directory.

MainActivity.java (2)

    ListView listView;
    ImageView imageView;
    TextView currentDirText;
    TextView numFilesText;
    Button backButton;
    String rootDir = "DCIM";
    String directoryName = rootDir; // Initialize to rootDirectory
    ArrayAdapter<String> listAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Set buttons
        getWindow().setTitleColor(Color.rgb(65, 183, 216));
        backButton = (Button)findViewById(R.id.button1);
        backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(directoryName.equals(rootDir)) {
                    listRootDirectory();
                }
                else {
                    int index = directoryName.lastIndexOf("/");
                    directoryName = directoryName.substring(0, index);
                    listDirectory(directoryName);
                }
            }
        });     
        backButton.setEnabled(false); // Disable in root directory
        listRootDirectory();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);       
        return true;
    }
  • Lines 17-32:
    We have set a Button that will allow the user to navigate to the parent directory. This feature is disabled when MainActivity is initialized because the app will start in the root directory and there would be no parent directory to navigate to.

It is worth noting that we have set the root directory to be the "DCIM" folder (line 6). You do not have to set this as your root directory. Since the "DCIM" folder is the location that most digital cameras store photos, it is set as the root directory for this tutorial.

Getting the Content Information for the Root Directory

The listRootDirectory() and listDirectory(directoryName) functions called in the function above are the functions that handle requesting and fetching content data from the FlashAir device. The listRootDirectory() function merely sets the directoryName class variable to the root directory for the FlashAir and then calls listDirectory(String dir) on it:

MainActivity.java (3)

    public void listRootDirectory() {
        directoryName = rootDir;
        listDirectory(directoryName);
    }

Getting the Content Information for Any Directory

We will set the back button to be disabled, when viewing contents of the home directory of the FlashAir.

MainActivity.java (4)

    public void listDirectory(String dir)
    { // Prepare command directory path
        if(dir.equals(rootDir)) {
            backButton.setEnabled(false);
        }
        else {
            backButton.setEnabled(true);
        }

Note that in lines 3-8, we set the back button to be either enabled or disabled. When viewing contents of the home directory of the FlashAir, the back button will be greyed out and unclickable as there is no further parent directory that will be traversed.

Therefore, the home directory screen will look like this:

display image in ImageView

In all other directories, the back button will be completely visible and clickable.

We will use the following CGI command to get the number of items in a directory:

  • command.cgi with op=101 and the directory as a parameter
    • The command will look like this: http://flashair/command.cgi?op=101&DIR=/DCIM
    • The command will return the following information about the directory: <NumberofItems>

We will use the following CGI command to retrieve a list of contents in a directory:

  • command.cgi with op=100 and the directory as a parameter
    • The command will look like this: http://flashair/command.cgi?op=100&DIR=/DCIM
    • The command will return the following information about each item in the directory: <Directory>,<Filename>,<Size>,<Attribute>,<Date>,<Time>

To execute this CGI command, we will reuse the FlashAirRequest.java file from Android Tutorial 2: Getting A List of Contents:

MainActivity.java (5)

        currentDirText = (TextView)findViewById(R.id.textView1);
        currentDirText.setText(dir + "/");
        // Fetch number of items in directory and display in a TextView
        dir = "/" + dir;
        ArrayList <NameValuePair> httpParams = new  ArrayList <NameValuePair> ();
        httpParams.add(new BasicNameValuePair("DIR", dir));
        dir = URLEncodedUtils.format (httpParams, "UTF-8" );
        numFilesText = (TextView)findViewById(R.id.textView2);
        // Fetch number of items in directory and display in a TextView
        new AsyncTask<String, Void, String>(){
            @Override
            protected String doInBackground(String... params) {
                String dir = params[0];
                String fileCount =  FlashAirRequest.getString("http://flashair/command.cgi?op=101&" + dir);
                return fileCount;
            }
            @Override
            protected void onPostExecute(String fileCount) {
                numFilesText.setText("Items Found: " + fileCount);                
            }
        }.execute(dir);
  • Line 7:
    We have set the directory name to have UTF-8 encoding.

The command command.cgi with op=100 will return the following information about each item in the directory: <Directory>,<Filename>,<Size>,<Attribute>,<Date>,<Time>
However, we only are interested in listing the names of each item in the directory. After this CGI command is executed, we will parse the data so it stores only the file names.

Again, since the class will keep track of the current and root directories, we will not need to manually type the folder name into the CGI command, but instead will use the directory name that was passed into the function.

MainActivity.java (6)

        // Fetch list of items in directory and display in a ListView
        new AsyncTask<String, Void, ListAdapter>(){
            @Override
            protected ListAdapter doInBackground(String... params) {
                String dir = params[0];
                ArrayList <String> fileNames = new ArrayList <String>();                
                String files = FlashAirRequest.getString("http://flashair/command.cgi?op=100&" + dir);
                String[] allFiles = files.split("([,\n])"); // split by newline or comma
                for(int i = 2; i < allFiles.length; i= i + 6) {
                    if(allFiles[i].contains(".")) {
                        // File
                        fileNames.add(allFiles[i]);
                    }
                    else { // Directory, append "/"
                        fileNames.add(allFiles[i] + "/");
                    }
                }

                listAdapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, fileNames);

                return listAdapter;
            }
            @Override
            protected void onPostExecute(ListAdapter listAdapter) {
                // Set the file list to a widget
                listView = (ListView)findViewById(R.id.listView1);
                ColorDrawable divcolor = new ColorDrawable(Color.rgb(17, 19, 58));
                listView.setDivider(divcolor);
                listView.setDividerHeight(1);
                listView.setAdapter(listAdapter);
                listView.setOnItemClickListener(MainActivity.this);
            }
        }.execute(dir);
  • Lines 8-17:
    We parse the returned string that contains the file information for the directory. From this string, we collect the name of each item in the directory. In lines 12-17, we check to see if the item is a file or a directory. This is done by checking for the beginning of a file extension. If it is a directory, we would like the name to display with a "/" at the end, as this is standard naming practice for directories. This will be useful later - in the onItemClick() function below, we will check for the "/" at the end of the item name in order to determine its click behavior.

Setting the Item Click Listener

The MainActivity class will now be able to draw a list of contents of the FlashAir to the screen in a ListView, but the list is currently not clickable. We need to implement the OnItemClickListener function from the MainActivity class declaration. This particular click listener will identify not only that the list was clicked, but specifically which list item was clicked.

We will set the list in the following manner:

  • If the item clicked is the name of a folder, the next screen will display the contents of the folder
  • If the item clicked is the name of an image file, that file will be downloaded, and the next screen will display its image in an ImageView

The original onItemClick function takes < AdapterView<?> l, View v, int position, long id > as arguments. In order to override the original function, our new function will also pass in those arguments.

MainActivity.java (7)

    @Override
    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
        Object downloadFile = l.getItemAtPosition(position); // get item at clicked position in list of files
        if(downloadFile.toString().endsWith("/")) { // Directory, remove "/" and show content list
            String dirName = downloadFile.toString().substring(0, downloadFile.toString().length()-1); // all but the "/"
            directoryName = directoryName + "/" + dirName;
            listDirectory(directoryName);
        }
        else if( downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpg") || downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg")
            || downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpe") || downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".png") )
        { // Image file, download using ImageViewActivity
            Intent viewImageIntent = new Intent(this, ImageViewActivity.class);
            viewImageIntent.putExtra("downloadFile", downloadFile.toString());
            viewImageIntent.putExtra("directoryName", directoryName);
            MainActivity.this.startActivity(viewImageIntent); 
        }
    }
} // End MainActivity class

As the image is actually downloaded and viewed using a second class ( class ImageViewActivity), we have set an Intent to start this activity (line 12).

We need class ImageViewActivity to be able to access the name of the file (stored in downloadFile.toString()) and the directory path (stored in directoryName) to the file that we wish to view.
In lines 13-14, we save these values as extra Bundle data within the Intent we just created. This allows us to send these values to ImageViewActivity when it is initialized (below).

Creating the Image Viewing Screen

Initialization

This class will also be an extension of the Activity class. We will set the ImageView that we use to show the image and Button that allows us to go back to the content list to be class variables. Our class declaration will look like this:

ImageViewActivity.java (1)

public class ImageViewActivity extends Activity {
    ImageView imageView;
    Button backButton;

We will also need to override the onCreate(Bundle savedInstanceState) and onCreateOptionsMenu(Menu menu) functions. In the onCreate(Bundle savedInstanceState) function, we will set the behavior of the back button.

ImageViewActivity.java (2)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_view);
        getIntent();
        imageView = (ImageView)findViewById(R.id.imageView1);
        backButton = (Button)findViewById(R.id.button2);
        getWindow().setTitleColor(Color.rgb(65, 183, 216));
        backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
        Bundle extrasData = getIntent().getExtras();
        String fileName = extrasData.getString("downloadFile");
        String directory = extrasData.getString("directoryName");
        downloadFile(fileName, directory);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.image_view, menu);
        return true;
    }
  • Line 10:
    We import the extra data that we sent from class MainActivity when we declared this Intent. This allows us to access the name of the file to download, as well as its directory path. We save these imported values in lines 11 and 12.

Downloading the File

Next, we will write the function that will download the file data from the FlashAir device.

We will need to add the following lines of function into the FlashAirRequest.java file from Android Tutorial 2: Getting A List of Contents in order for the file to view:

FlashAirRequest.java

    static public Bitmap getBitmap(String command) {            
        Bitmap resultBitmap = null;
        try{
            URL url = new URL(command);
            URLConnection urlCon = url.openConnection();
            urlCon.connect();
            InputStream inputStream = urlCon.getInputStream();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] byteChunk = new byte[1024];
            int bytesRead = 0;
            while( (bytesRead = inputStream.read(byteChunk)) != -1) {
                byteArrayOutputStream.write(byteChunk, 0, bytesRead);
            }
            byte[] byteArray = byteArrayOutputStream.toByteArray();
            BitmapFactory.Options bfOptions = new BitmapFactory.Options();
            bfOptions.inPurgeable = true;
            resultBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, bfOptions);
            byteArrayOutputStream.close();
            inputStream.close();
        }catch(MalformedURLException e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }
        catch(IOException e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }
        return resultBitmap;                        
    }

Lines 15-16 will give us permission to purge the Bitmap that we create (in line 20). This will allow us to manage memory more efficiently, as the memory allocated can be freed later if needed.

We will use the file name and directory that was passed through the Intent to reconstruct the path to the file. Using this information, we can retrieve the file data.

We will use the getBitmap() that we just added:

ImageViewActivity.java (3)

    void downloadFile(String downloadFile, String directory) {
        final ProgressDialog waitDialog;        
        // Setting ProgressDialog
        waitDialog = new ProgressDialog(this);
        waitDialog.setMessage("Now downloading...");
        waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        waitDialog.show();                
        // Download file
        new AsyncTask<String, Void, Bitmap>(){
            @Override
            protected Bitmap doInBackground(String... params) {
                String fileName = params[0];
                return FlashAirRequest.getBitmap(fileName);
            }
            @Override
            protected void onPostExecute(Bitmap resultBitmap) {
                waitDialog.dismiss();                                
                viewImage(resultBitmap);
            }
        }.execute("http://flashair/" + directory + "/" + downloadFile.toString());    
    }

Viewing the Image in App

The call to viewImage() in the function above is what displays the image to the screen after the image Bitmap is fetched. This function will display the image in an ImageView.

ImageViewActivity.java (4)

    void viewImage(Bitmap imageBitmap) {
        // Show image in ImageView
        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ImageViewActivity.this.finish();
            }
        });     
        if (imageBitmap == null) {
            imageView.setImageResource(R.drawable.ic_launcher);
        }
        else {
            imageView.setImageBitmap(imageBitmap);
        }
    }
} // End ImageViewActivity class

Result

The clicked image will be drawn to the screen like this:

This image shows image view

Sample Code

android_tutorial_03.zip (539KB)

All sample code on this page is licensed under BSD 2-Clause License